package ca.uvic.cs.cogz.mapping.ui;

import java.awt.BorderLayout;
import java.awt.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.swing.AbstractCellEditor;
import javax.swing.BorderFactory;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;

import ca.uvic.cs.cogz.mapping.core.AbstractMapElement;
import ca.uvic.cs.cogz.mapping.core.VerifiedMapElement;
import ca.uvic.cs.cogz.mapping.ontology.DataLoaderFacade;
import ca.uvic.cs.cogz.mapping.ontology.TransformationEngine;
import ca.uvic.cs.cogz.util.FrameUtil;
import edu.stanford.smi.protege.model.Cls;
import edu.stanford.smi.protege.model.Slot;
import edu.stanford.smi.protegex.owl.model.RDFProperty;
import edu.stanford.smi.protegex.owl.model.impl.DefaultOWLDatatypeProperty;

/**
 * Property view editor for a verified mapping.  Allows a user to set values on target element properties.
 * 
 * @author seanf
 */
public class ClsPropertyPanel extends JPanel {
	private static final long serialVersionUID = -5005341840441785618L;
	
	private Map<String, Map<Slot, Object>> mappingToSlotValueMap;
	private Map<Slot, Object> currentSlotToValueMap;
	
	private Slot[] slots;
	private VerifiedMapElement currentMapElement;
	private Cls currentCls;
	
	private PropertyTableEditor propertyTableEditor;
	private PropertyTableRenderer propertyRenderer;
	private PropertyTableModel propertyTableModel;
	private JTable propertyTable;

	public ClsPropertyPanel() {
		init();
		
		mappingToSlotValueMap = new HashMap<String, Map<Slot,Object>>();
	}
	
	/**
	 * Sets up the property view for the given mapElement
	 * @param mapElement
	 */
	public void setSelectedMapping(VerifiedMapElement mapElement) {
		// make sure this is a cls to cls mapping
		if(!(mapElement.getSourceFrame() instanceof Cls) && !(mapElement.getTargetFrame() instanceof Cls)) {
			return;
		}
		
		currentMapElement = mapElement;
		currentSlotToValueMap = mappingToSlotValueMap.get(currentMapElement.toString());
		
		currentCls = (Cls)mapElement.getTargetFrame();
		slots = (Slot[])FrameUtil.getDomainRangeSlots(currentCls).toArray(new Slot[FrameUtil.getDomainRangeSlots(currentCls).size()]);
		
		if(currentSlotToValueMap == null) {
			currentSlotToValueMap = new HashMap<Slot, Object>();
			
			for(Slot slot : slots) currentSlotToValueMap.put(slot, null);
			
			mappingToSlotValueMap.put(currentMapElement.toString(), currentSlotToValueMap);
		}
		
		propertyTableModel.fireTableDataChanged();
	}
	
	public void setSelectedCls(Cls cls) {
		currentCls = cls;
		slots = (Slot[])FrameUtil.getDomainRangeSlots(currentCls).toArray(new Slot[FrameUtil.getDomainRangeSlots(currentCls).size()]);
		
		propertyTableModel.fireTableDataChanged();
	}
	
	private void init() {
		JScrollPane scrollPanel = new JScrollPane(getPropertyTable());
		setLayout(new BorderLayout());
		add(scrollPanel, BorderLayout.CENTER);
	}
	
	private JTable getPropertyTable() {
		if(propertyTable == null) {
			propertyTable = new JTable();
			
			propertyTableModel = new PropertyTableModel();
			propertyTable.setModel(propertyTableModel);
			
			propertyTableEditor = new PropertyTableEditor();
			propertyTable.setDefaultEditor(Object.class, propertyTableEditor);
			
			propertyRenderer = new PropertyTableRenderer();
			propertyTable.setDefaultRenderer(Object.class, propertyRenderer);
			
			propertyTable.getColumnModel().getColumn(0).setCellRenderer(propertyRenderer);
			propertyTable.getColumnModel().getColumn(1).setCellRenderer(propertyRenderer);
			
			propertyTable.getColumnModel().getColumn(0).setWidth(50);
			propertyTable.getColumnModel().getColumn(0).setPreferredWidth(50);
		}
		
		return propertyTable;
	}
	
	/**
	 * Property model for the target frame's slots.
	 */
	class PropertyTableModel extends AbstractTableModel {
		private static final long serialVersionUID = 9176204911713043052L;
		
		private String[] columnNames = {"Property", "Value"};

		public String getColumnName(int col) {
	        return columnNames[col].toString();
	    }
		
		public boolean isCellEditable(int row, int col) { 
			if(col == 0) return false;
			
			// check if this slot has mappings, if so, the user can't edit it
			Slot slot = slots[row];
			List<AbstractMapElement> mappedElements = DataLoaderFacade.getInstance().getMappingCollection().getMappedElements(slot);
			
			return mappedElements == null || mappedElements.size() == 0;
		}
		
		public int getColumnCount() {
			return columnNames.length;
		}

		public int getRowCount() {
			if(currentCls == null) return 0;
			
			return slots.length;
		}

		public Object getValueAt(int rowIndex, int columnIndex) {
			return slots[rowIndex];
		}
		
		public void setValueAt(Object value, int row, int col) {
			Slot slot = (Slot)getValueAt(row, 0);
			
			Object currentValue = currentSlotToValueMap.get(slot);
			
			// do not set a blank value if there isn't a value already set
			if(currentValue == null && value.toString().equals("")) return;
			
			currentSlotToValueMap.put(slot, value);
			TransformationEngine.getInstance().setPropertyValue(currentMapElement, slot, value.toString());
			
			fireTableCellUpdated(row, col);
		}
	}
	
	/**
	 * Table renderer for properties.  The first column is rendered as the slot name and icon.  The second column
	 * is the assigned value, either through a mapping or specified value.
	 */
	class PropertyTableRenderer extends DefaultTableCellRenderer {
		private static final long serialVersionUID = -7964532273340976604L;

		public Component getTableCellRendererComponent(JTable table, Object value,
                boolean isSelected, boolean hasFocus, int row, int column) {
			
			JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
			Slot slot = (Slot)value;
			if(column == 0) {
				RDFProperty property = (RDFProperty)slot;
				
				String range = "";
				if(property.getRange() != null) {
					range = ":" + FrameUtil.cleanFrameName(property.getRange().getBrowserText());
				}
				label.setText(FrameUtil.cleanFrameName(slot.getBrowserText()) + range);
				label.setIcon(slot.getIcon());
			}
			else {
				label.setIcon(null);
				Object assignedValue = currentSlotToValueMap.get(slot);
				if(assignedValue != null) {
					label.setText(assignedValue.toString());
				}
				else {
					// check if this slot has any mappings, if so, set that to the value of this label
					List<AbstractMapElement> mappedElements = DataLoaderFacade.getInstance().getMappingCollection().getMappedElements(slot);
					if(mappedElements != null) {
						label.setText(mappedElements.get(0).getSourceFrame().getBrowserText());
						label.setIcon(mappedElements.get(0).getSourceFrame().getIcon());
					}
					else {
						label.setText("");
					}
				}
			}
			
			return label;
		}
	}
	
	/**
	 * Editor for properties.  Reference slots have a drop down list as their editor where the user can select
	 * any instantiated class.  Data type properties have a textfield where a user can enter a value.
	 */
	class PropertyTableEditor extends AbstractCellEditor
						    implements TableCellEditor {
		private static final long serialVersionUID = -3211067502063777442L;
		
		private int currentRow;
		private JTextField textField;
		private JComboBox list;
		
		public PropertyTableEditor() {
			textField = new JTextField();
			textField.setBorder(BorderFactory.createEmptyBorder());
			
			list = new JComboBox();
		}

		public Component getTableCellEditorComponent(JTable table,
				Object value, boolean isSelected, int row, int column) {
			
			currentRow = row;
			
			Slot slot = slots[currentRow];
			
			if(slot instanceof DefaultOWLDatatypeProperty) {
				Object slotValue = currentSlotToValueMap.get(slot);
				if(slotValue != null) {
					textField.setText(slotValue.toString());
				}
				else {
					textField.setText("");
				}
				
				return textField;
			}
			else {
				list.removeAllItems();
				list.addItem("");
				for(String variableNames : TransformationEngine.getInstance().getInstatiatedVariableNames()) {
					list.addItem(variableNames);
				}
				
				return list;
			}
		}

		public Object getCellEditorValue() {
			Slot slot = slots[currentRow];
			if(slot instanceof DefaultOWLDatatypeProperty) {
				return textField.getText();
			}
			else {
				return list.getSelectedItem().toString();
			}
		}
	}
}
